#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# This program is free software; you can redistribute it and/or modify  
# it under the terms of the GNU General Public License as published by  
# the Free Software Foundation; either version 2 of the License, or     
# (at your option) any later version.                                   
#                                                                         
# A copy of the license can be found in the license.txt file supplied   
# with this software or at: http://www.gnu.org/copyleft/gpl.html       
#


import sys,time

from spinage.help.codecchain import CodecChain

from spinage.net import ioloop,iostream
from spinage.help import logging

from response import RC_OK,RC_WAITING,RC_TIMEOUT
from session import SessionPool


class   Accessor(object) :

    DEFAULT_TIMEOUT_MS = 300 * 1000

    # index of response status group (self._responses{ request } --> [ RI_STATUS_CODE, RI_RESPONSE ]
    RI_STATUS_CODE = 0
    RI_RESPONSE    = 1
    
    def __init__(self) :
        self._logger = logging.getLogger(self.__class__.__name__)

        self._io_loop = ioloop.IOLoop()

        self._sessions = {}

        self._session_pool = SessionPool( self._io_loop )
        ## __init__()

    def __del__(self) :

        del self._session_pool
        self._session_pool = None

        ## __del__()

    ###################################################################

    def getCodecChain(self, peer=None) :
        return  self._session_pool.getCodecChain( peer )
        ## getCodecChain()

    def setCodecChain(self, codec_chain, peer=None) :
        self._session_pool.setCodecChain(codec_chain, peer)
        ## setCodecChain()

    def setProtocolFactory(self, protocol_factory, peer=None) :
        self._session_pool.setProtocolFactory(protocol_factory, peer)
        ## setProtocolFactory()

    def getProtocolFactory(self, peer=None) :
        return self._session_pool.getProtocolFactory( peer )
        ## getProtocolFactory()

    ###################################################################

    def call(self, request, address, timeout_ms = -1) :
        '''call-multiple server concurrently

        @param request request data
        @param address address where service locates
        @param timeout_ms timeout when calling remote response

        @return (state_code, response)
        '''
        if timeout_ms <= 0 :
            timeout_ms = self.DEFAULT_TIMEOUT_MS
        timeout_s = timeout_ms / 1000

        # STEP. for each request :
        #         . get session
        #         . send request
        #         . prepare response collection
        self._responses = {}
        self._sessions_ongoing = {}

        _session = self._session_pool.acquire( address )
        _session.sendrecv( request, self.__onDataReceived )

        self._sessions_ongoing[ _session ] = True  # not done
        self._responses[ request ] = [ RC_WAITING, None ]

        # STEP. set timeout
        self._timeout_called = False
        deadline = time.time() + timeout_s
        self._timeout_handle = self._io_loop.add_timeout( deadline, self.__onTimeoutCall )

        # STEP. action! now!!
        self._io_loop.start()

        # STEP. if timeout, reset all timeout-session
        if self._timeout_called :
            for req,status in self._responses.iteritems() :
                if status[ self.RI_STATUS_CODE ] == RC_WAITING :
                    status[ self.RI_STATUS_CODE ] = RC_TIMEOUT

            for _session,ongoing in self._sessions_ongoing.iteritems() :
                # cancel any un-done session
                if  ongoing :
                    _session.cancel()

            for _session,ongoing in self._sessions_ongoing.iteritems() :
                self._session_pool.release( _session )
            self._sessions_ongoing = None

            return self._responses[ request ]
        else :
            self._io_loop.remove_timeout( self._timeout_handle )
            self._timeout_handle = None

        for _session,done in self._sessions_ongoing.iteritems() :
            self._session_pool.release( _session )
        self._sessions_ongoing = None

        return self._responses[ request ]
        ## call()
    
    ###################################################################

    def mcall(self, requests, timeout_ms = -1) :
        '''call-multiple server concurrently

        @param requests Map<request, peer-address>
        @param timeout_ms timeout when calling remote response

        @return Map<request, [state_code, response]>
        '''
        if timeout_ms <= 0 :
            timeout_ms = self.DEFAULT_TIMEOUT_MS
        timeout_s = timeout_ms / 1000

        # STEP. for each request :
        #         . get session
        #         . send request
        #         . prepare response collection
        self._responses = {}
        self._sessions_ongoing = {}
        for request,address in requests.iteritems() :
            _session = self._session_pool.acquire( address )
            _session.sendrecv( request, self.__onDataReceived )

            self._sessions_ongoing[ _session ] = True
            self._responses[ request ] = [ RC_WAITING, None ]

        # STEP. set timeout
        self._timeout_called = False
        deadline = time.time() + timeout_s
        self._timeout_handle = self._io_loop.add_timeout( deadline, self.__onTimeoutCall )

        # STEP. action! now!!
        self._io_loop.start()

        # STEP. if timeout, reset all timeout-session
        if self._timeout_called :
            for req,status in self._responses.iteritems() :
                if status[ self.RI_STATUS_CODE ] == RC_WAITING :
                    status[ self.RI_STATUS_CODE ] = RC_TIMEOUT

            for _session,ongoing in self._sessions_ongoing.iteritems() :
                # cancel any un-done session
                if ongoing :
                    _session.cancel()

            for _session,ongoing in self._sessions_ongoing.iteritems() :
                self._session_pool.release( _session )
            self._sessions_ongoing = None

            return self._responses
        else :
            self._io_loop.remove_timeout( self._timeout_handle )
            self._timeout_handle = None

        self._sessions_ongoing = None

        return self._responses
        ## mcall()
    
    def __onDataReceived(self, session, status_code, data_or_error, request=None) :
        if request :
            if request in self._responses :
                status = self._responses[ request ]
                status[ self.RI_STATUS_CODE ] = status_code
                status[ self.RI_RESPONSE ] = data_or_error

        if session in self._sessions_ongoing :
            # done
            self._sessions_ongoing[ session ] = False

        all_done = True
        for _session,ongoing in self._sessions_ongoing.iteritems() :
            if ongoing :
                all_done = False
                break
        if all_done :
            # no any session alive
            self._io_loop.stop()
        ## __onDataReceived()

    def __onTimeoutCall(self) :
        self._io_loop.stop()
        self._timeout_called = True
        ## __onTimeoutCall()


    ###################################################################



    def rrcall(self, address_list, request, timeout_ms = -1) :
        '''Read-Repair Call
        '''
        if timeout_ms <= 0 :
            timeout_ms = self.DEFAULT_TIMEOUT_MS
        timeout_s = timeout_ms / 1000

        # STEP. get sessions
        sessions = self.__getSessions( address_list )

        # STEP. prepare response collector

        # STEP. send request by each session
        for s in sessions :
            s.sendrecv( request, self.__onDataReceived )
        
        # STEP. set timeout
        self._timeout_called = False
        deadline = time.time() + timeout_s
        self._timeout_handle = self._io_loop.add_timeout( deadline, self.__onTimeoutCall )

        # STEP. if timeout, reset all timeout-session
        if self._timeout_called :
            raise NotImplemented
        else :
            self._io_loop.remove_timeout( self._timeout_handle )
            self._timeout_handle = None

        # STEP. call CALLBACK to evaluate result
        result, to_repair = self._evaluate_callback( result_set )

        # STEP. to do some post-actions
        if to_repair :
            raise NotImplemented

        # STEP. return result
        return result
        ## rrcall()

    ###################################################################

    def __getSessions_UNUSED(self, address_list) :
        sessions = []

        for address in address_list :
            if address not in self._sessions :
                _session = Session(address, self.__getIOLoop())
                _session.setCodecChain( self.getCodecChain( address ) )
                _session.setProtocol( self.getProtocol( address ) )
                self._sessions[ address ] = _session

            sessions.append( self._sessions[ address ] )

        return sessions
        ## __getSessions()


    def __getIOLoop(self) :
        return  self._io_loop
        ## __getIOLoop()

    ## class Accessor()



